﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using PluginInterfacesLib;
using System.Drawing;
using System.IO;
using Emgu.CV.Structure;
using Emgu.CV;
using Emgu.CV.UI;
using System.Windows.Forms;
using System.Windows.Input;
using System.Threading;
using System.ComponentModel;
using System.Windows.Controls;
using SoundPlugin.Custom_Buttons;
using System.Windows;
using System.Runtime.InteropServices;

namespace SoundPlugin
{
    public class SoundPlugin : IPlugin
    {

        #region Properties

        [System.Runtime.InteropServices.DllImportAttribute("user32.dll", EntryPoint = "SetCursorPos")]
        [return: System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.Bool)]
        public static extern bool SetCursorPos(int X, int Y);

        [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
        public static extern void mouse_event(uint dwFlags, uint dx, uint dy, uint dwData, UIntPtr dwExtraInfo);

        [Flags]
        public enum MouseEventFlags : uint
        {
            LEFTDOWN = 0x00000002,
            LEFTUP = 0x00000004,
            MIDDLEDOWN = 0x00000020,
            MIDDLEUP = 0x00000040,
            MOVE = 0x00000001,
            ABSOLUTE = 0x00008000,
            RIGHTDOWN = 0x00000008,
            RIGHTUP = 0x00000010
        }



        private String name;
        public String Name 
        { 
            get 
            {
                return name;
            }
        }

        private Shapes shapeControl;
        private SoundToShapeMenu soundToShapeMenu;
        private ShapeSoundProcessor processor;
        private IPluginHost host;
        private Thread pluginModelThread;
        private List<Shape> shapes;
        private List<FileInfo> allSounds;
        private List<Thread> shapeProcessThreads;
        private Bitmap shapeImage;
        private double displayWidth;
        
        public int NrOfShapes
        {
            get 
            {
                return this.shapes.Count;
            }
        }

        public IPluginHost Host 
        {
            get 
            {
                return host;
            }
            set 
            {
                this.host = value;
            }
        }

        private Shape selectedShape;
        private ReaderWriterLockSlim selectedShapeLock = new ReaderWriterLockSlim();
        public Shape SelectedShape
        {
            get
            {
                try
                {
                    selectedShapeLock.EnterReadLock();
                    return selectedShape;
                }
                finally
                {
                    selectedShapeLock.ExitReadLock();
                }
            }
            set
            {
                try
                {
                    selectedShapeLock.EnterWriteLock();
                    selectedShape = value;
                }
                finally
                {
                    selectedShapeLock.ExitWriteLock();
                }
            }
        }

        private CancellationTokenSource cancelMarkerImageThread = new CancellationTokenSource();
        private Thread markerImageThread;

        private bool isRunning = false;
        private ReaderWriterLockSlim isRunningLock = new ReaderWriterLockSlim();
        public bool IsRunning
        {
            get
            {
                try
                {
                    isRunningLock.EnterReadLock();
                    return isRunning;
                }
                finally
                {
                    isRunningLock.ExitReadLock();
                }
            }
            set
            {
                try
                {
                    isRunningLock.EnterWriteLock();
                    isRunning = value;
                }
                finally
                {
                    isRunningLock.ExitWriteLock();
                }
            }
        }

        private static System.Windows.Controls.UserControl imageLoaded;
        private static CancellationTokenSource cancelSource = new CancellationTokenSource();
        #endregion

        #region Constructors
        public SoundPlugin() 
        {
            this.name = "Sound Plugin";
        }
        #endregion

        #region Public Methods
        public void Initialize() 
        {
            this.host.AddButtonToPluginBar(0,"Detect", "Detect Shapes",HandleDetectShapes);
            this.host.AddButtonToPluginBar(4,"AssignSounds", "Assign Sounds",HandleSoundMapping);
            this.shapes = new List<Shape>();
            this.allSounds = new List<FileInfo>();
            this.shapeProcessThreads = new List<Thread>();
            this.processor = new ShapeSoundProcessor(this);
            this.processor.GetAllSounds();
            this.shapeControl = new Shapes(HandleMouseClick);
            this.host.AddOverlappingWindow(this.shapeControl);
            this.shapeControl.Loaded += new System.Windows.RoutedEventHandler(SoundPlugin_Loaded);

            PluginModel.Instance.Host = this.host;
            PluginModel.Instance.RunThread = true;
            pluginModelThread = new Thread(new ThreadStart(PluginModel.Instance.ThreadRun));
            pluginModelThread.Name = "PluginModelThread in SoundPlugin";
            pluginModelThread.Start();

            this.markerImageThread = new Thread(new ParameterizedThreadStart(this.AddColorMarkerImageThread));
            this.markerImageThread.Name = "AddMarkerImageThread in SoundPlugin";
            this.cancelMarkerImageThread = new CancellationTokenSource();
            //this.markerImageThread.Start(this.cancelMarkerImageThread.Token);

            SoundPlugin.cancelSource = new CancellationTokenSource();
            SoundPlugin.StartColorMarkerPorcessor(SoundPlugin.cancelSource.Token);

        }

        public void Initialize(IPluginHost Host) 
        {

        }

        public void StartPlugin() 
        {
            if (this.IsRunning) 
            {
                return;
            }

            if (this.shapes.Count == 0) 
            {
                return;    
            }


            int nrOfUnassignedShapes = CheckUnassignedShapes();
            DisplayChoiceDialog(this.shapes.Count, nrOfUnassignedShapes);
            StartProcessingShapes();
            this.IsRunning = true;
        }

        public void StopPlugin()
        {
            StopProcessingShapes();
            PluginModel.Instance.RunThread = false;
            this.pluginModelThread.Join();
            SoundPlugin.cancelSource.Cancel();
            this.cancelMarkerImageThread.Cancel();
        }

        public Bitmap GetPluginImage()
        {
            Stream stream = this.GetType().Assembly.GetManifestResourceStream("SoundPlugin.1.jpg");
            return new Bitmap(stream);
        }


        public void AddShape(Shape shape)
        {
            this.shapes.Add(shape);
        }

        public void ClearShapes()
        {
            this.shapes.Clear();
        }

        public void AddSound(FileInfo sound)
        {
            this.allSounds.Add(sound);
        }

        public void ClearSounds()
        {
            this.allSounds.Clear();
        }

        public void BuildShapeImage(Bitmap image)
        {
            Image<Bgr, byte> img = new Image<Bgr, byte>(image);
            foreach (Shape shape in this.shapes)
            {
                img.FillConvexPoly(shape.Contour.ToArray(), new Bgr(shape.Color));
            }

            Bitmap bitmapImage = img.Bitmap;
            bitmapImage.MakeTransparent(Color.Black);
            this.shapeImage = bitmapImage;
        }

        public void Dispose()
        {

        }

        public void Run()
        {
            this.processor.RunDetect();
            if (this.shapes.Count != 0)
            {
                foreach (Shape shape in this.shapes)
                {
                    shape.PropertyChanged += this.HandleShape_PropertyChanged;
                    shape.Processor = this.processor;
                }
                this.shapeControl.Dispatcher.BeginInvoke(
                    new Action(
                        delegate()
                        {
                            this.shapeControl.BlankImage = new Bitmap(this.shapeImage.Width, this.shapeImage.Height);
                            this.shapeControl.ChangeImage(this.shapeImage);
                            
                            PluginModel.Instance.Plugin = this;
                        }

                        )
                    );
            }
            this.processor.GetAllSounds();
            this.shapeControl.Dispatcher.BeginInvoke(
                    new Action(
                        delegate()
                        {
                            this.soundToShapeMenu = new SoundToShapeMenu(this.allSounds);
                            this.soundToShapeMenu.SoundListBox.SelectionChanged += new SelectionChangedEventHandler(HandleSoundListBoxItemChanged);
                            this.soundToShapeMenu.RightButton.MouseLeftButtonUp += new MouseButtonEventHandler(HandleRightTriangleClick);
                            this.soundToShapeMenu.LeftButton.MouseLeftButtonUp += new MouseButtonEventHandler(HandleLeftTriangleClick);
                            this.soundToShapeMenu.FinishedButton.Click += new RoutedEventHandler(HandleFinishedButtonClick);
                            this.soundToShapeMenu.AssignRandomlyButton.Click += new RoutedEventHandler(HandleAssingRandomlyButtonClick);
                            this.shapeControl.SoundToShapeMenu = this.soundToShapeMenu;
                        }
                        )
                    );

        }

        public void CheckButtons(System.Drawing.Point p)
        {
            //SetCursorPos(p.X, p.Y);
            double widthDisplay = this.displayWidth;
            double widthWebcamImage = ((Bitmap)this.host.GetWebcamImage()).Width;
            double ratio = widthDisplay / widthWebcamImage;
            System.Drawing.Point point = new System.Drawing.Point((int)(p.X), (int)(p.Y));
            foreach (Shape shape in this.shapes)
            {
                if (shape.Contour.InContour(point) >= 0)
                {
                    if (shape.SoundFile != null)
                    {
                        this.processor.PlaySound(shape, true, false);
                        break;
                    }
                }
            }
        }
        #endregion

        #region Private Methods
        private void StartProcessingShapes()
        {
            foreach (Shape shape in this.shapes)
            {
                shape.Processor = this.processor;
                shape.Process = true;
                Thread thread = new Thread(new ThreadStart(shape.ProcessShape));
                this.shapeProcessThreads.Add(thread);
            }
            foreach (Thread thread in this.shapeProcessThreads) 
            {
                thread.Start();
            }
        }

        private void StopProcessingShapes() 
        {
            foreach (Shape shape in this.shapes) 
            {
                shape.Process = false;
            }
            foreach (Thread thread in this.shapeProcessThreads)
            {
                thread.Join();
            }
            this.shapeProcessThreads.Clear();
        }

        private void PauseShapeProcessing() 
        { 
            foreach (Shape shape in this.shapes)
            {
                shape.IsPaused = true;
            }
        }

        private void UnPauseShapeProcessing() 
        {
            foreach (Shape shape in this.shapes)
            {
                shape.IsPaused = false;
            }
        }

        private void DisplayChoiceDialog(int nrOfShapes, int nrOfUnassignedShapes)
        {
            
        }

        private int CheckUnassignedShapes()
        {
            int nrOfUnassignedShapes = 0;
            foreach (Shape shape in this.shapes)
            {
                if(shape.SoundFile == null)
                {
                    nrOfUnassignedShapes++;
                }
            }
            return nrOfUnassignedShapes;
        }

        private static double ratio;

        private void SoundPlugin_Loaded(object sender, System.Windows.RoutedEventArgs e)
        {
            this.displayWidth = ((System.Windows.Controls.UserControl)sender).ActualWidth;
            double widthWebcamImage = 640;
            SoundPlugin.ratio = displayWidth / widthWebcamImage;
            SoundPlugin.imageLoaded = (System.Windows.Controls.UserControl)sender;
        }

        private void AssignAllSoundsRandomly()
        {
            Random random = new Random();
            //his.processor.GetAllSounds();
            for (int i = 0; i < this.shapes.Count; i++)
            {
                if (this.shapes[i].SoundFile == null)
                {
                    int rand = random.Next(this.allSounds.Count);
                    this.processor.AssignSoundToShape(ref this.shapes, i, this.allSounds[rand]);
                    this.shapes[i].Process = true;
                }
            }
        }

        private static void StartColorMarkerPorcessor(CancellationToken cancel) 
        {
            Thread thread = new Thread(new ParameterizedThreadStart(ProcessColorMarkerProcessor));
            thread.Start(cancel);
        }

        private static void ProcessColorMarkerProcessor(object cancelObject)
        {
            try
            {
                Thread.Sleep(3000);
                CancellationToken cancel = (CancellationToken)cancelObject;
                int nrOfMarkers = PluginModel.Instance.MarkerPositions.Count;
                double[, ,] distances = new double[nrOfMarkers, nrOfMarkers, 10];
                int index = 0;
                bool breaker = false;
                while (true) 
                {
                    cancel.ThrowIfCancellationRequested();
                    if (index == 9) 
                    {
                        index = 0;
                    }
                    List<Spat> markers = new List<Spat>(PluginModel.Instance.MarkerPositions);
                    for (int i = 0; i < markers.Count; i++)
                    {
                        for (int j = 0; j < markers.Count; j++)
                        {
                            if (i != j)
                            {
                                try
                                {
                                    System.Drawing.Point marker1 = (System.Drawing.Point)markers[i].getCenter();
                                    System.Drawing.Point marker2 = (System.Drawing.Point)markers[j].getCenter();
                                    distances[i, j, index] = SoundPlugin.CalculateDistance(marker1, marker2);
                                    double maximum;
                                    double averageDistance = SoundPlugin.CalculateSumAndMaximum(distances, i, j, out maximum) / 10;
                                    float ratio = (float)(averageDistance / maximum);
                                    if ((averageDistance < 40) && (ratio > 0.75) && (ratio < 1.3) && (averageDistance > 10))
                                    {
                                        System.Drawing.Point clickLocation = SoundPlugin.CalculateMidpoint(marker1, marker2);
                                        SoundPlugin.ColorMarkerClick(clickLocation);
                                        distances = new double[nrOfMarkers, nrOfMarkers, 10];
                                        breaker = true;
                                        Thread.Sleep(100);
                                        break;
                                    }
                                    Console.WriteLine("Marker {0} , Marker {1} \t Average distance: {2} , Maximum = {3} , Ration = {4} ", i, j, averageDistance, maximum, ratio);
                                }
                                catch (InvalidCastException ex)
                                {
                                    distances[i, j, index] = 0;
                                }
                                catch (InvalidOperationException ex)
                                {
                                    distances[i, j, index] = 0;
                                    breaker = true;
                                    break;
                                }
                            }
                        }
                        if (breaker) 
                        {
                            breaker = false;
                            break;
                        }
                    }
                    index++;
                    Thread.Sleep(40);
                }
            }
            catch (System.OperationCanceledException ex)
            {

            }
            finally 
            { 
            
            }
        }

        private delegate System.Windows.Point myDelegate(System.Windows.Point p);
        private static void ColorMarkerClick(System.Drawing.Point clickLocation)
        {
            //throw new NotImplementedException();
            System.Windows.Point point = new System.Windows.Point(clickLocation.X * SoundPlugin.ratio, clickLocation.Y * SoundPlugin.ratio);

            System.Windows.Threading.DispatcherOperation operation =
            SoundPlugin.imageLoaded.Dispatcher.BeginInvoke(Delegate.CreateDelegate(typeof(myDelegate), SoundPlugin.imageLoaded, "PointToScreen"), System.Windows.Threading.DispatcherPriority.Send, point);
            operation.Completed += new EventHandler(operation_Completed);
            //mouse_event((uint)(MouseEventFlags.LEFTDOWN | MouseEventFlags.LEFTUP), x, y, 0, UIntPtr.Zero);
            
        }

        private static double CalculateSumAndMaximum(double[,,] matrix, int marker1, int marker2, out double maximum)
        {
            maximum = -1;
            double sum = 0;
            for (int i = 0; i < 10; i++)
            {
                double currentDistance = matrix[marker1, marker2, i];
                if (maximum < currentDistance)
                    maximum = currentDistance;
                sum = sum + currentDistance;
            }
            if (maximum == 0) 
            {
                maximum = -1;
            }
            return sum;
        }
        private static double CalculateDistance(System.Drawing.Point p1, System.Drawing.Point p2) 
        {
            if (p1 == null || p2 == null)
                return -1;
            return Math.Sqrt((p2.X - p1.X) * (p2.X - p1.X) + (p2.Y - p1.Y) * (p2.Y - p1.Y));
        }

        private static System.Drawing.Point CalculateMidpoint(System.Drawing.Point p1, System.Drawing.Point p2)
        {
            return new System.Drawing.Point((int)((p1.X + p2.X)/2),(int)((p1.Y + p2.Y)/2));
        }

        private void AddColorMarkerImageThread(object cancelObject) 
        {
            CancellationToken cancel = (CancellationToken)cancelObject;
            //Thread.Sleep(10000);
            try 
            {
                while (true)
                {
                    cancel.ThrowIfCancellationRequested();
                    this.shapeControl.Dispatcher.BeginInvoke
                    (
                        new Action
                        (
                            delegate()
                            {
                                this.shapeControl.AddMarkerImage(PluginModel.Instance.MarkerImage);
                            }
                        )
                    );
                    Thread.Sleep(30);
                }
            }
            catch(OperationCanceledException ex) 
            { 
            
            }
        }

        #endregion

        #region Event Handlers

        public void HandleDetectShapes(object sender, EventArgs e)
        {

            if (this.processor == null) 
            {
                this.processor = new ShapeSoundProcessor(this);
            }

            if (this.processor.Detect) 
            {
                return;
            }

            Thread PluginThread = new Thread(new ThreadStart(Run));
            PluginThread.Start();            
        }


        static private void operation_Completed(object sender, EventArgs e) 
        {
            System.Windows.Threading.DispatcherOperation operation = (System.Windows.Threading.DispatcherOperation)sender;
            System.Windows.Point point = (System.Windows.Point)operation.Result;
            SetCursorPos(Convert.ToInt32(point.X), Convert.ToInt32(point.Y));
            mouse_event((uint)(MouseEventFlags.LEFTDOWN | MouseEventFlags.ABSOLUTE), (uint)point.X, (uint)point.Y, 0, UIntPtr.Zero);
            mouse_event((uint)(MouseEventFlags.LEFTUP | MouseEventFlags.ABSOLUTE), (uint)point.X, (uint)point.Y, 0, UIntPtr.Zero);
        }

        public void HandleSoundMapping(object sender, EventArgs e)
        {
            if (this.processor == null) 
            {
                this.processor = new ShapeSoundProcessor(this);
            }
            if (this.shapes.Count != 0)
            {
                this.processor.GetAllSounds();
                if (this.allSounds.Count != 0) 
                {
                    this.soundToShapeMenu = new SoundToShapeMenu(this.allSounds);
                    this.SelectedShape = this.shapes[0];
                    this.SelectedShape.IsActive = true;
                    this.shapeControl.Dispatcher.BeginInvoke(
                        new Action(
                            delegate()
                            {
                                //SoundPlugin.cancelSource = new CancellationTokenSource();
                                this.shapeControl.ShowSoundToShapeMenu();
                                //SoundPlugin.StartColorMarkerPorcessor(SoundPlugin.cancelSource.Token);
                            }
                        ));
                }
            }
        }

        public void HandleMouseClick(object sender, EventArgs e) 
        {
            MouseButtonEventArgs mEventArgs = (MouseButtonEventArgs)e;
            System.Windows.Point pos = mEventArgs.GetPosition((System.Windows.IInputElement)sender);
            double widthDisplay = ((System.Windows.Controls.Image)this.shapeControl.MainGrid.Children[0]).ActualWidth;
            double widthWebcamImage = ((Bitmap)this.host.GetWebcamImage()).Width;
            double ratio = widthDisplay / widthWebcamImage;
            System.Drawing.Point point = new System.Drawing.Point((int)(pos.X/ratio), (int)(pos.Y/ratio));
            foreach (Shape shape in this.shapes)
            {
                
 
                if (shape.Contour.InContour(point) >= 0)
                {
                    if (shape.SoundFile != null)
                    {
                        this.processor.PlaySound(shape, false, false);
                        shape.IsActive = true;
                        return;
                    }
                    else 
                    {
                        //this.PauseShapeProcessing();
                        if (this.SelectedShape != null) 
                        {
                            this.SelectedShape.IsActive = false;
                            this.shapeControl.Dispatcher.BeginInvoke(
                                new Action(
                                    delegate()
                                    {
                                        this.shapeControl.HideSoundToShapeMenu();
                                        //SoundPlugin.cancelSource.Cancel();
                                    }
                            ));
                        }
                        this.SelectedShape = shape;
                        this.SelectedShape.IsActive = true;
                        this.shapeControl.Dispatcher.BeginInvoke(
                            new Action(
                                delegate()
                                {
                                    shape.Processor = this.processor;
                                    //SoundPlugin.cancelSource = new CancellationTokenSource();
                                    this.shapeControl.ShowSoundToShapeMenu();
                                    //SoundPlugin.StartColorMarkerPorcessor(SoundPlugin.cancelSource.Token);
                                }
                        ));
                        return;
                    }
                }
            }
            if (this.SelectedShape != null) 
            {
                this.selectedShape.IsActive = false;
                this.SelectedShape = null;
            }
            //this.UnPauseShapeProcessing();
            this.shapeControl.Dispatcher.BeginInvoke(
                            new Action(
                                delegate()
                                {
                                    this.shapeControl.HideSoundToShapeMenu();
                                    //SoundPlugin.cancelSource.Cancel();
                                }
                        ));
        }

        private void HandleSoundListBoxItemChanged(object sender, EventArgs e)
        {
            System.Windows.Controls.ListBox listBox = (System.Windows.Controls.ListBox)sender;
            ListBoxItem item = (ListBoxItem)listBox.SelectedItem;
            FileInfo info = (FileInfo)item.Content;
            this.SelectedShape.SoundFile = info;
            this.SelectedShape.Process = true;
            this.SelectedShape.Processor.PlaySound(this.selectedShape, false, true);
        }

        private void HandleShape_PropertyChanged(object sender, PropertyChangedEventArgs e) 
        {
            Shape shape = (Shape)sender;
            Boolean action = Boolean.Parse(e.PropertyName);
            this.shapeControl.Dispatcher.BeginInvoke(
                    new Action(
                        delegate()
                        {
                            if (action)
                            {
                                this.shapeControl.AddContour(shape.Contour , shape.HighlightColor);
                            }
                            else 
                            {
                                this.shapeControl.RemoveContour(shape.Contour);
                            }
                        }

                        )

                    );
        }

        private void HandleRightTriangleClick(object sender, EventArgs e) 
        {
            TriangleButton button = (TriangleButton)sender;
            button.Opacity = 1;
            if (this.shapes.Count == 0) 
            {
                return;
            }
            if (this.SelectedShape == null) 
            {
                this.SelectedShape = this.shapes[0];
                this.SelectedShape.IsActive = true;
                return;
            }
            this.SelectedShape.IsActive = false;
            int index = this.shapes.IndexOf(this.SelectedShape);
            index++;
            if (index == this.shapes.Count) 
            {
                index = 0;
            }
            this.SelectedShape = this.shapes[index];
            this.SelectedShape.IsActive = true;
            button.Opacity = 0.5;
        }

        private void HandleLeftTriangleClick(object sender, EventArgs e)
        {
            TriangleButton button = (TriangleButton)sender;
            button.Opacity = 1;
            if (this.shapes.Count == 0)
            {
                return;
            }
            if (this.SelectedShape == null)
            {
                this.SelectedShape = this.shapes[0];
                this.SelectedShape.IsActive = true;
                return;
            }
            this.SelectedShape.IsActive = false;
            int index = this.shapes.IndexOf(this.SelectedShape);
            index--;
            if (index < 0)
            {
                index = this.shapes.Count - 1;
            }
            this.SelectedShape = this.shapes[index];
            this.SelectedShape.IsActive = true;
            button.Opacity = 0.5;
        }

        private void HandleAssingRandomlyButtonClick(object sender, EventArgs e) 
        {
            System.Windows.Controls.Button button = (System.Windows.Controls.Button)sender;
            button.Opacity = 0.8;
            this.AssignAllSoundsRandomly();
            this.selectedShapeLock.EnterWriteLock();
            if (this.selectedShape != null) 
            {
                this.selectedShape.IsActive = false;
            }
            this.selectedShapeLock.ExitWriteLock();
            this.shapeControl.Dispatcher.BeginInvoke(
            new Action(
                delegate()
                {
                    this.shapeControl.HideSoundToShapeMenu();
                    //SoundPlugin.cancelSource.Cancel();
                }
            ));
            button.Opacity = 0.5;
        }

        private void HandleFinishedButtonClick(object sender, EventArgs e) 
        {
            System.Windows.Controls.Button button = (System.Windows.Controls.Button)sender;
            button.Opacity = 0.8;
            this.selectedShapeLock.EnterWriteLock();
            if (this.selectedShape != null) 
            {
                this.selectedShape.IsActive = false;
            }
            this.selectedShapeLock.ExitWriteLock();

            this.SelectedShape = null;
            this.shapeControl.Dispatcher.BeginInvoke(
                new Action(
                    delegate()
                    {
                        this.shapeControl.HideSoundToShapeMenu();
                        //SoundPlugin.cancelSource.Cancel();
                    }
                ));
            button.Opacity = 0.5;
        }
 
        #endregion

    }
}
